/*
 * ads1115.c:
 *	Extend wiringPi with the ADS1115 I2C 16-bit ADC
 *	Copyright (c) 2016 Gordon Henderson
 ***********************************************************************
 * This file is part of wiringPi:
 *	https://github.com/WiringPi/WiringPi/
 *
 *    wiringPi is free software: you can redistribute it and/or modify
 *    it under the terms of the GNU Lesser General Public License as
 *    published by the Free Software Foundation, either version 3 of the
 *    License, or (at your option) any later version.
 *
 *    wiringPi is distributed in the hope that it will be useful,
 *    but WITHOUT ANY WARRANTY; without even the implied warranty of
 *    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *    GNU Lesser General Public License for more details.
 *
 *    You should have received a copy of the GNU Lesser General Public
 *    License along with wiringPi.
 *    If not, see <http://www.gnu.org/licenses/>.
 ***********************************************************************
 */

/*
 *********************************************************************************
 * We're going to work in a hybrid mode to fit in with the wiringPi way of
 * doing things, so there will be 4 analog pin which read the 4 single-ended
 * channels as usual, also some fake digitalOutputs - these are the control
 * registers that allow the user to put it into single/diff mode, set the
 * gain and data rates.
 *********************************************************************************
 */

#include <byteswap.h>
#include <stdio.h>
#include <stdint.h>

#include <wiringPi.h>
#include <wiringPiI2C.h>

#include "ads1115.h"

// Bits in the config register (it's a 16-bit register)

#define	CONFIG_OS_MASK		(0x8000)	// Operational Status Register
#define	CONFIG_OS_SINGLE	(0x8000)	// Write - Starts a single-conversion
						// Read    1 = Conversion complete

// The multiplexor

#define	CONFIG_MUX_MASK		(0x7000)

// Differential modes

#define	CONFIG_MUX_DIFF_0_1	(0x0000)	// Pos = AIN0, Neg = AIN1 (default)
#define	CONFIG_MUX_DIFF_0_3	(0x1000)	// Pos = AIN0, Neg = AIN3
#define	CONFIG_MUX_DIFF_1_3	(0x2000)	// Pos = AIN1, Neg = AIN3
#define	CONFIG_MUX_DIFF_2_3	(0x3000)	// Pos = AIN2, Neg = AIN3 (2nd differential channel)

// Single-ended modes

#define	CONFIG_MUX_SINGLE_0	(0x4000)	// AIN0
#define	CONFIG_MUX_SINGLE_1	(0x5000)	// AIN1
#define	CONFIG_MUX_SINGLE_2	(0x6000)	// AIN2
#define	CONFIG_MUX_SINGLE_3	(0x7000)	// AIN3

// Programmable Gain Amplifier

#define	CONFIG_PGA_MASK		(0x0E00)
#define	CONFIG_PGA_6_144V	(0x0000)	// +/-6.144V range = Gain 2/3
#define	CONFIG_PGA_4_096V	(0x0200)	// +/-4.096V range = Gain 1
#define	CONFIG_PGA_2_048V	(0x0400)	// +/-2.048V range = Gain 2 (default)
#define	CONFIG_PGA_1_024V	(0x0600)	// +/-1.024V range = Gain 4
#define	CONFIG_PGA_0_512V	(0x0800)	// +/-0.512V range = Gain 8
#define	CONFIG_PGA_0_256V	(0x0A00)	// +/-0.256V range = Gain 16

#define	CONFIG_MODE		(0x0100)	// 0 is continuous, 1 is single-shot (default)

// Data Rate

#define	CONFIG_DR_MASK		(0x00E0)
#define	CONFIG_DR_8SPS		(0x0000)	//   8 samples per second
#define	CONFIG_DR_16SPS		(0x0020)	//  16 samples per second
#define	CONFIG_DR_32SPS		(0x0040)	//  32 samples per second
#define	CONFIG_DR_64SPS		(0x0060)	//  64 samples per second
#define	CONFIG_DR_128SPS	(0x0080)	// 128 samples per second (default)
#define	CONFIG_DR_475SPS	(0x00A0)	// 475 samples per second
#define	CONFIG_DR_860SPS	(0x00C0)	// 860 samples per second

// Comparator mode

#define	CONFIG_CMODE_MASK	(0x0010)
#define	CONFIG_CMODE_TRAD	(0x0000)	// Traditional comparator with hysteresis (default)
#define	CONFIG_CMODE_WINDOW	(0x0010)	// Window comparator

// Comparator polarity - the polarity of the output alert/rdy pin

#define	CONFIG_CPOL_MASK	(0x0008)
#define	CONFIG_CPOL_ACTVLOW	(0x0000)	// Active low (default)
#define	CONFIG_CPOL_ACTVHI	(0x0008)	// Active high

// Latching comparator - does the alert/rdy pin latch

#define	CONFIG_CLAT_MASK	(0x0004)
#define	CONFIG_CLAT_NONLAT	(0x0000)	// Non-latching comparator (default)
#define	CONFIG_CLAT_LATCH	(0x0004)	// Latching comparator

// Comparitor queue

#define	CONFIG_CQUE_MASK	(0x0003)
#define	CONFIG_CQUE_1CONV	(0x0000)	// Assert after one conversions
#define	CONFIG_CQUE_2CONV	(0x0001)	// Assert after two conversions
#define	CONFIG_CQUE_4CONV	(0x0002)	// Assert after four conversions
#define	CONFIG_CQUE_NONE	(0x0003)	// Disable the comparator (default)

#define	CONFIG_DEFAULT		(0x8583)	// From the datasheet


static const uint16_t dataRates [8] =
{
  CONFIG_DR_8SPS, CONFIG_DR_16SPS, CONFIG_DR_32SPS, CONFIG_DR_64SPS, CONFIG_DR_128SPS, CONFIG_DR_475SPS, CONFIG_DR_860SPS
} ;

static const uint16_t gains [6] =
{
  CONFIG_PGA_6_144V, CONFIG_PGA_4_096V, CONFIG_PGA_2_048V, CONFIG_PGA_1_024V, CONFIG_PGA_0_512V, CONFIG_PGA_0_256V
} ;


/*
 * analogRead:
 *	Pin is the channel to sample on the device.
 *	Channels 0-3 are single ended inputs,
 *	channels 4-7 are the various differential combinations.
 *********************************************************************************
 */

static int myAnalogRead (struct wiringPiNodeStruct *node, int pin)
{
  int chan = pin - node->pinBase ;
  int16_t  result ;
  uint16_t config = CONFIG_DEFAULT ;

  chan &= 7 ;

// Setup the configuration register

//	Set PGA/voltage range

  config &= ~CONFIG_PGA_MASK ;
  config |= node->data0 ;

//	Set sample speed

  config &= ~CONFIG_DR_MASK ;
  config |= node->data1 ;

//	Set single-ended channel or differential mode

  config &= ~CONFIG_MUX_MASK ;

  switch (chan)
  {
    case 0: config |= CONFIG_MUX_SINGLE_0 ; break ;
    case 1: config |= CONFIG_MUX_SINGLE_1 ; break ;
    case 2: config |= CONFIG_MUX_SINGLE_2 ; break ;
    case 3: config |= CONFIG_MUX_SINGLE_3 ; break ;

    case 4: config |= CONFIG_MUX_DIFF_0_1 ; break ;
    case 5: config |= CONFIG_MUX_DIFF_2_3 ; break ;
    case 6: config |= CONFIG_MUX_DIFF_0_3 ; break ;
    case 7: config |= CONFIG_MUX_DIFF_1_3 ; break ;
  }

//	Start a single conversion

  config |= CONFIG_OS_SINGLE ;
  config = __bswap_16 (config) ;
  wiringPiI2CWriteReg16 (node->fd, 1, config) ;

// Wait for the conversion to complete

  for (;;)
  {
    result =  wiringPiI2CReadReg16 (node->fd, 1) ;
    result = __bswap_16 (result) ;
    if ((result & CONFIG_OS_MASK) != 0)
      break ;
    delayMicroseconds (100) ;
  }

  result =  wiringPiI2CReadReg16 (node->fd, 0) ;
  result = __bswap_16 (result) ;

// Sometimes with a 0v input on a single-ended channel the internal 0v reference
//	can be higher than the input, so you get a negative result...

  if ( (chan < 4) && (result < 0) ) 
    return 0 ;
  else
    return (int)result ;
}


/*
 * digitalWrite:
 *	It may seem odd to have a digital write here, but it's the best way
 *	to pass paramters into the chip in the wiringPi way of things.
 *	We have 2 digital registers:
 *		0 is the gain control
 *		1 is the data rate control
 *********************************************************************************
 */

static void myDigitalWrite (struct wiringPiNodeStruct *node, int pin, int data)
{
  int chan = pin - node->pinBase ;
  chan &= 3 ;

  if (chan == 0)	// Gain Control
  {
    if ( (data < 0) || (data > 6) )	// Use default if out of range
      data = 2 ;
    node->data0 = gains [data] ;
  }
  else			// Data rate control
  {
    if ( (data < 0) || (data > 7) )	// Use default if out of range
      data = 4 ;
    node->data1 = dataRates [data] ;	// Bugfix 0-1 by "Eric de jong (gm)" <ericdejong@gmx.net> - Thanks.
  }
  
}


/*
 * analogWrite:
 *	We're using this to write to the 2 comparitor threshold registers.
 *	We could use a digitalWrite here but as it's an analog comparison
 *	then it feels better to do it this way.
 *********************************************************************************
 */

static void myAnalogWrite (struct wiringPiNodeStruct *node, int pin, int data)
{
  int chan = pin - node->pinBase ;
  int reg ;
  int16_t ndata ;

  chan &= 3 ;

  reg = chan + 2 ;

  /**/ if (data < -32767)
    ndata = -32767 ;
  else if (data > 32767)
    ndata = 32767 ;
  else
    ndata = (int16_t)data ;

  ndata = __bswap_16 (ndata) ;
  wiringPiI2CWriteReg16 (node->fd, reg, data) ;
}



/*
 * ads1115Setup:
 *	Create a new wiringPi device node for an ads1115 on the Pi's
 *	I2C interface.
 *********************************************************************************
 */

int ads1115Setup (const int pinBase, int i2cAddr)
{
  struct wiringPiNodeStruct *node ;
  int fd ;

  if ((fd = wiringPiI2CSetup (i2cAddr)) < 0)
    return FALSE ;

  node = wiringPiNewNode (pinBase, 8) ;

  node->fd           = fd ;
  node->data0        = CONFIG_PGA_4_096V ;	// Gain in data0
  node->data1        = CONFIG_DR_128SPS ;	// Samples/sec in data1
  node->analogRead   = myAnalogRead ;
  node->analogWrite  = myAnalogWrite ;
  node->digitalWrite = myDigitalWrite ;

  return TRUE ;
}
